import cfg from '../config/config.js';
import WebSocket from 'ws';

const devices = new Map();
const deviceCommands = new Map();
const deviceData = new Map();
const deviceStats = new Map();
const deviceWebSockets = new Map();
const deviceLogs = new Map();
const pendingLogs = new Map();

class DeviceManager {
  constructor() {
    this.heartbeatInterval = cfg.device?.heartbeat_interval || 30;
    this.heartbeatTimeout = cfg.device?.heartbeat_timeout || 120;
    this.maxDevices = cfg.device?.max_devices || 100;
    this.allowAutoRegister = cfg.device?.allow_auto_register !== false;
    this.commandTimeout = cfg.device?.command_timeout || 10000;
    this.maxLogsPerDevice = 1000;
    this.maxDataPerDevice = 100;
  }

  sanitizeJsonData(data) {
    if (typeof data === 'string') {
      return data.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '');
    }
    if (typeof data === 'object' && data !== null) {
      const cleaned = {};
      for (const key in data) {
        if (data.hasOwnProperty(key)) {
          cleaned[key] = this.sanitizeJsonData(data[key]);
        }
      }
      return cleaned;
    }
    return data;
  }

  async registerDevice(deviceData, Bot, ws = null) {
    try {
      deviceData = this.sanitizeJsonData(deviceData);
      
      const { 
        device_id, 
        device_type, 
        device_name, 
        capabilities = [], 
        metadata = {},
        ip_address,
        firmware_version
      } = deviceData;
      
      if (!device_id || !device_type) {
        throw new Error('Missing required parameters: device_id and device_type');
      }

      if (devices.size >= this.maxDevices && !devices.has(device_id)) {
        throw new Error(`Device limit reached (${this.maxDevices})`);
      }

      const existingDevice = devices.get(device_id);
      const device = {
        device_id,
        device_type,
        device_name: device_name || `${device_type}_${device_id}`,
        capabilities,
        metadata,
        ip_address,
        firmware_version,
        online: true,
        last_seen: Date.now(),
        registered_at: existingDevice?.registered_at || Date.now(),
        stats: existingDevice?.stats || {
          messages_sent: 0,
          messages_received: 0,
          commands_executed: 0,
          errors: 0,
          uptime: 0,
          logs_count: 0
        }
      };

      devices.set(device_id, device);
      
      // Process pending logs
      if (pendingLogs.has(device_id)) {
        const logs = pendingLogs.get(device_id);
        deviceLogs.set(device_id, logs);
        pendingLogs.delete(device_id);
        logger.info(`[Device] Restored ${logs.length} pending logs`);
      } else {
        deviceLogs.set(device_id, []);
      }
      
      this.addDeviceLog(device_id, 'info', 'Device registered', {
        device_type,
        device_name: device.device_name,
        ip_address,
        capabilities
      });
      
      if (ws) {
        deviceWebSockets.set(device_id, ws);
        ws.device_id = device_id;
      }
      
      if (!Bot.bots[device_id]) {
        Bot.uin.push(device_id);
      }
      
      Bot.bots[device_id] = {
        uin: device_id,
        nickname: device.device_name,
        adapter: { name: 'device', id: device_id },
        device: true,
        isOnline: true,
        info: device,
        capabilities,
        metadata,
        sendMsg: async (msg) => this.sendMessage(device_id, msg),
        sendCommand: async (cmd, params) => this.sendCommand(device_id, cmd, params),
        getLogs: () => this.getDeviceLogs(device_id)
      };

      Bot.em('device.online', {
        post_type: 'device',
        event_type: 'online',
        device_id,
        device_type,
        device_name: device.device_name,
        capabilities,
        time: Math.floor(Date.now() / 1000)
      });

      logger.info(`[Device] ${device.device_name} (${device_id}) - IP: ${ip_address}`);
      return device;
    } catch (error) {
      logger.error(`Device registration failed: ${error.message}`);
      throw error;
    }
  }

  addDeviceLog(deviceId, level, message, data = {}) {
    if (!devices.has(deviceId)) {
      const logs = pendingLogs.get(deviceId) || [];
      const logEntry = {
        timestamp: Date.now(),
        level,
        message: String(message).substring(0, 500),
        data: this.sanitizeJsonData(data),
        formatted: `[${new Date().toLocaleString()}] [${level.toUpperCase()}] ${message}`
      };
      logs.unshift(logEntry);
      if (logs.length > 100) logs.splice(100);
      pendingLogs.set(deviceId, logs);
      return logEntry;
    }

    const logs = deviceLogs.get(deviceId) || [];
    const logEntry = {
      timestamp: Date.now(),
      level,
      message: String(message).substring(0, 500),
      data: this.sanitizeJsonData(data),
      formatted: `[${new Date().toLocaleString()}] [${level.toUpperCase()}] ${message}`
    };
    
    logs.unshift(logEntry);
    
    if (logs.length > this.maxLogsPerDevice) {
      logs.splice(this.maxLogsPerDevice);
    }
    
    deviceLogs.set(deviceId, logs);
    
    const device = devices.get(deviceId);
    if (device?.stats) {
      device.stats.logs_count++;
      if (level === 'error') {
        device.stats.errors++;
      }
    }
    
    return logEntry;
  }

  async processDeviceEvent(deviceId, eventType, eventData = {}, Bot) {
    try {
      if (!devices.has(deviceId) && eventType === 'message') {
        this.addDeviceLog(deviceId, 
          eventData.level || 'info', 
          eventData.text || 'Device message', 
          eventData.metadata || {}
        );
        return { success: true, pending: true };
      }

      const device = devices.get(deviceId);
      if (!device) {
        if (eventType !== 'message') {
          throw new Error('Device not registered');
        }
        return { success: false, error: 'Device not registered' };
      }

      eventData = this.sanitizeJsonData(eventData);

      device.last_seen = Date.now();
      device.online = true;

      const stats = deviceStats.get(deviceId) || {};
      stats.messages_received = (stats.messages_received || 0) + 1;
      deviceStats.set(deviceId, stats);

      if (eventType === 'message') {
        const { level = 'info', text, category = 'device', metadata = {} } = eventData;
        
        this.addDeviceLog(deviceId, level, text || 'Device message', metadata);
        
        const logText = `[Device][${device.device_name}][${category}] ${text}`;
        switch(level) {
          case 'error':
            logger.error(logText);
            break;
          case 'warn':
          case 'warning':
            logger.warn(logText);
            break;
          case 'debug':
            logger.debug(logText);
            break;
          default:
            logger.info(logText);
        }
      }

      const storedData = deviceData.get(deviceId) || [];
      storedData.push({
        timestamp: Date.now(),
        event_type: eventType,
        data: eventData
      });
      if (storedData.length > this.maxDataPerDevice) {
        storedData.shift();
      }
      deviceData.set(deviceId, storedData);

      const eventPayload = {
        post_type: 'device',
        event_type: eventType,
        device_id: deviceId,
        device_type: device.device_type,
        device_name: device.device_name,
        event_data: eventData,
        capabilities: device.capabilities,
        time: Math.floor(Date.now() / 1000)
      };

      Bot.em(`device.${eventType}`, eventPayload);
      
      if (eventType === 'sensor' && eventData.sensor_type) {
        Bot.em(`device.sensor.${eventData.sensor_type}`, eventPayload);
      }

      return { success: true, event_id: eventPayload.time };
    } catch (error) {
      logger.error(`Event processing failed: ${error.message}`);
      return { success: false, error: error.message };
    }
  }

  async updateHeartbeat(deviceId, status = {}) {
    const device = devices.get(deviceId);
    if (!device) return { commands: [], status: {} };

    const wasOffline = !device.online;
    device.last_seen = Date.now();
    device.online = true;
    
    status = this.sanitizeJsonData(status);
    
    if (status.heap_free !== undefined || status.uptime !== undefined) {
      this.addDeviceLog(deviceId, 'debug', 'Heartbeat', {
        heap_free: status.heap_free,
        uptime: status.uptime,
        plugins_loaded: status.plugins_loaded
      });
    }
    
    if (status) {
      device.status = { ...device.status, ...status };
      if (status.uptime) {
        device.stats.uptime = status.uptime;
      }
    }

    if (wasOffline && Bot.bots[deviceId]) {
      Bot.bots[deviceId].isOnline = true;
      this.addDeviceLog(deviceId, 'info', 'Device online');
      Bot.em('device.online', {
        post_type: 'device',
        event_type: 'online',
        device_id: deviceId,
        time: Math.floor(Date.now() / 1000)
      });
    }

    const commands = deviceCommands.get(deviceId) || [];
    if (commands.length > 0) {
      deviceCommands.set(deviceId, []);
      this.addDeviceLog(deviceId, 'debug', `Sending ${commands.length} commands`);
    }

    return { commands, status: device.status };
  }

  async sendCommand(deviceId, command, parameters = {}, priority = 0) {
    const device = devices.get(deviceId);
    if (!device) {
      throw new Error('Device not found');
    }

    const cmd = {
      id: Date.now().toString(),
      command,
      parameters: this.sanitizeJsonData(parameters),
      priority,
      timestamp: Date.now()
    };

    const commands = deviceCommands.get(deviceId) || [];
    commands.push(cmd);
    commands.sort((a, b) => b.priority - a.priority);
    deviceCommands.set(deviceId, commands);

    device.stats.commands_executed++;
    this.addDeviceLog(deviceId, 'info', `Command queued: ${command}`, parameters);

    return {
      success: true,
      command_id: cmd.id,
      queued_commands: commands.length
    };
  }

  checkOfflineDevices(Bot) {
    const timeout = this.heartbeatTimeout * 1000;
    const now = Date.now();

    for (const [id, device] of devices) {
      if (device.online && now - device.last_seen > timeout) {
        device.online = false;
        this.addDeviceLog(id, 'warn', 'Device offline', { 
          last_seen: new Date(device.last_seen).toLocaleString() 
        });
        
        if (Bot.bots[id]) {
          Bot.bots[id].isOnline = false;
        }
        
        const ws = deviceWebSockets.get(id);
        if (ws && ws.readyState === WebSocket.OPEN) {
          ws.close();
        }
        deviceWebSockets.delete(id);
        
        Bot.em('device.offline', {
          post_type: 'device',
          event_type: 'offline',
          device_id: id,
          device_type: device.device_type,
          device_name: device.device_name,
          time: Math.floor(Date.now() / 1000)
        });

        logger.warn(`[Device offline] ${device.device_name} (${id})`);
      }
    }
  }

  getDeviceList() {
    return Array.from(devices.values()).map(d => ({
      ...d,
      stats: deviceStats.get(d.device_id) || d.stats,
      has_websocket: deviceWebSockets.has(d.device_id),
      recent_logs: this.getDeviceLogs(d.device_id, { limit: 5 })
    }));
  }

  getDeviceLogs(deviceId, filter = {}) {
    const logs = deviceLogs.get(deviceId) || [];
    let filtered = [...logs];
    
    if (filter.level) {
      filtered = filtered.filter(log => log.level === filter.level);
    }
    
    if (filter.since) {
      const sinceTime = new Date(filter.since).getTime();
      filtered = filtered.filter(log => log.timestamp >= sinceTime);
    }
    
    if (filter.limit) {
      filtered = filtered.slice(0, filter.limit);
    }
    
    return filtered;
  }

  async processWebSocketMessage(ws, data, Bot) {
    try {
      const { type, device_id, ...payload } = data;
      
      if (type === 'register') {
        const device = await this.registerDevice({
          device_id,
          ...payload,
          ip_address: ws._socket.remoteAddress
        }, Bot, ws);
        
        ws.send(JSON.stringify({
          type: 'register_response',
          success: true,
          device
        }));
      } else if (type === 'event') {
        const result = await this.processDeviceEvent(
          device_id || ws.device_id,
          payload.event_type,
          payload.event_data,
          Bot
        );
        
        ws.send(JSON.stringify({
          type: 'event_response',
          ...result
        }));
      } else if (type === 'heartbeat') {
        const result = await this.updateHeartbeat(
          device_id || ws.device_id,
          payload.status
        );
        
        ws.send(JSON.stringify({
          type: 'heartbeat_response',
          ...result
        }));
      }
    } catch (error) {
      logger.error(`WebSocket message processing failed: ${error.message}`);
      ws.send(JSON.stringify({
        type: 'error',
        message: error.message
      }));
    }
  }
}

const deviceManager = new DeviceManager();

export default {
  name: 'device',
  description: 'Device Management API',

  routes: [
    {
      method: 'POST',
      path: '/api/device/register',
      handler: async (req, res, Bot) => {
        try {
          const ip_address = req.ip || req.socket.remoteAddress;
          const device = await deviceManager.registerDevice({
            ...req.body,
            ip_address
          }, Bot);
          
          res.json({
            success: true,
            device_id: device.device_id,
            device_name: device.device_name,
            websocket_url: `ws://${req.get('host')}/device`
          });
        } catch (error) {
          logger.error(`Registration failed: ${error.message}`);
          res.status(400).json({ success: false, message: error.message });
        }
      }
    },

    {
      method: 'POST',
      path: '/api/device/heartbeat',
      handler: async (req, res) => {
        try {
          const { device_id, status } = req.body;
          const result = await deviceManager.updateHeartbeat(device_id, status);
          res.json({ 
            success: true, 
            commands: result.commands,
            timestamp: Date.now()
          });
        } catch (error) {
          res.status(404).json({ success: false, message: error.message });
        }
      }
    },

    {
      method: 'POST',
      path: '/api/device/event',
      handler: async (req, res, Bot) => {
        try {
          const { device_id, event_type, event_data } = req.body;
          const result = await deviceManager.processDeviceEvent(
            device_id, event_type, event_data, Bot
          );
          res.json(result);
        } catch (error) {
          res.status(404).json({ success: false, message: error.message });
        }
      }
    },

    {
      method: 'GET',
      path: '/api/devices',
      handler: async (req, res) => {
        const devices = deviceManager.getDeviceList();
        res.json({ 
          success: true, 
          devices,
          stats: {
            total: devices.length,
            online: devices.filter(d => d.online).length,
            offline: devices.filter(d => !d.online).length
          }
        });
      }
    },

    {
      method: 'GET',
      path: '/api/device/:deviceId/logs',
      handler: async (req, res) => {
        try {
          const { deviceId } = req.params;
          const { level, since, limit } = req.query;
          
          const logs = deviceManager.getDeviceLogs(deviceId, {
            level,
            since,
            limit: limit ? parseInt(limit) : undefined
          });
          
          res.json({
            success: true,
            device_id: deviceId,
            logs,
            total: logs.length
          });
        } catch (error) {
          res.status(404).json({ success: false, message: error.message });
        }
      }
    },

    {
      method: 'POST',
      path: '/api/device/:deviceId/command',
      handler: async (req, res) => {
        try {
          const { deviceId } = req.params;
          const { command, parameters, priority } = req.body;
          
          deviceManager.addDeviceLog(deviceId, 'info', `Execute: ${command}`, parameters);
          
          const result = await deviceManager.sendCommand(
            deviceId, command, parameters, priority
          );
          res.json(result);
        } catch (error) {
          res.status(400).json({ success: false, message: error.message });
        }
      }
    }
  ],

  init(app, Bot) {
    Bot.wsf.device = [(ws, req) => {
      ws.on('message', msg => {
        try {
          const data = JSON.parse(msg);
          deviceManager.processWebSocketMessage(ws, data, Bot);
        } catch (error) {
          logger.error(`WebSocket parse failed: ${error.message}`);
        }
      });
      
      ws.on('close', () => {
        if (ws.device_id) {
          deviceWebSockets.delete(ws.device_id);
          deviceManager.addDeviceLog(ws.device_id, 'info', 'WebSocket closed');
          logger.info(`[WebSocket] Device ${ws.device_id} disconnected`);
        }
      });
      
      ws.on('error', (error) => {
        logger.error(`WebSocket error: ${error.message}`);
      });
    }];

    setInterval(() => {
      deviceManager.checkOfflineDevices(Bot);
    }, 30000);
    
    setInterval(() => {
      for (const [deviceId, logs] of deviceLogs) {
        if (logs.length > deviceManager.maxLogsPerDevice) {
          logs.splice(deviceManager.maxLogsPerDevice);
        }
      }
      
      for (const [deviceId, data] of deviceData) {
        if (data.length > deviceManager.maxDataPerDevice) {
          data.splice(deviceManager.maxDataPerDevice);
        }
      }
      
      for (const [deviceId, logs] of pendingLogs) {
        const now = Date.now();
        const filtered = logs.filter(log => now - log.timestamp < 3600000);
        if (filtered.length !== logs.length) {
          pendingLogs.set(deviceId, filtered);
        }
        if (filtered.length === 0) {
          pendingLogs.delete(deviceId);
        }
      }
    }, 3600000);
    
    logger.info('[Device Manager] Initialized');
  }
};